package com.hqd.jjvm.rtda.heap;

import com.hqd.jjvm.classformat.InstructionCode;
import com.hqd.jjvm.classformat.MethodInfo;
import com.hqd.jjvm.classformat.attributeinfo.*;
import com.hqd.jjvm.classformat.constantpool.ConstantPool;
import com.hqd.jjvm.rtda.heap.accesss.MethodAccessFlag;
import com.hqd.jjvm.rtda.heap.ref.JClassRef;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.util.ClassUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class JMethod extends JClassMember {
    private Integer maxStack;
    private Integer maxLocals;
    private InstructionCode code;
    private Integer argSlotCount;
    private List<JMethodExceptionTable> catchExceptionTable;
    private List<JClassRef> throwExceptionTable;
    private List<JLineNumber> lineNumberTable;
    private List<String> paramType;
    private byte[] paramAnnos;

    public JMethod(MethodInfo methodInfo, JClass jClass, List<ConstantPool> constantPools) {
        this.argSlotCount = new Integer(0);
        this.catchExceptionTable = new ArrayList<>();
        this.throwExceptionTable = new ArrayList<>();
        this.lineNumberTable = new ArrayList<>();
        this.paramType = new ArrayList<>();
        newJMethod(methodInfo, jClass, constantPools);
    }

    public int getLineNumber(int starPC) {
        if (this.isNative()) {
            return -2;
        }
        if (CollectionUtils.isEmpty(this.lineNumberTable)) {
            return -1;
        }
        for (JLineNumber lm : this.lineNumberTable) {
            if (starPC == lm.getStartPC().intValue()) {
                return lm.getLineNumber();
            }
        }
        return -1;
    }

    public int findExceptionHandler(JClass exJClass, int pc) {
        try {
            for (JMethodExceptionTable ex : catchExceptionTable) {
                if (pc >= ex.startPC && pc <= ex.endPC) {
                    if (ex.getExceptionClassRef() == null) {
                        return 0;
                    }
                    JClass catchJClass = new JClassRef(ex.getExceptionClassRef()).getJClass();
                    if (exJClass.equals(catchJClass) || catchJClass.isSuperClassOf(exJClass)) {
                        return 1;
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return -1;
    }

    @Override
    protected void newDescInfo(AttributeInfo attr, JClass jClass, List<ConstantPool> constantPool) {
        if (attr instanceof Code) {
            Code code = (Code) attr;
            this.setCode(code.getInstructionCode());
            this.setMaxStack(code.getMaxStack());
            this.setMaxLocals(code.getMaxLocals());
            resolveExceptionTable(code, constantPool);
            resolveAttrs(code, constantPool);
        } else if (attr instanceof Exceptions) {
            this.resolveExceptions((Exceptions) attr, constantPool);
        } else if (attr instanceof RuntimeVisibleParameterAnnotations) {
            RuntimeVisibleParameterAnnotations paramAnno = (RuntimeVisibleParameterAnnotations) attr;
            this.paramAnnos = paramAnno.getByteCodes();
        }
    }

    private void newJMethod(MethodInfo methodInfo, JClass jClass, List<ConstantPool> constantPool) {
        super.newJClassMember(methodInfo, jClass, constantPool);
        resolveArgSlot();
        resolveParams();
    }

    private void resolveParams() {
        String desc = this.getDescriptor();
        String paramStr = desc.substring(desc.indexOf("(") + 1, desc.lastIndexOf(")"));
        String className = "";
        for (int i = 0; i < paramStr.length(); i++) {
            if (ClassUtil.isPrimitive(paramStr.charAt(i))) {
                className = String.valueOf(paramStr.charAt(i));
            } else {
                try {
                    int index = paramStr.indexOf(";", i);
                    if (index == -1) {
                        index = paramStr.length();
                    }
                    className = paramStr.substring(i, index);
                    if (className.startsWith("L")) {
                        className = className.substring(1);
                    }
                    i = index;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            paramType.add(className);
        }
    }

    private void resolveExceptions(Exceptions ex, List<ConstantPool> constantPool) {
        List<Integer> indexList = ex.getExceptionIndexTable();
        if (CollectionUtils.isNotEmpty(indexList)) {
            for (Integer index : indexList) {
                JClassRef jClassRef = (JClassRef) constantPool.get(index).getVal();
                this.throwExceptionTable.add(jClassRef);
            }
        }
    }

    private void resolveAttrs(Code code, List<ConstantPool> constantPool) {
        List<AttributeInfo> attrs = code.getAttributeInfos();
        if (CollectionUtils.isNotEmpty(attrs)) {
            for (AttributeInfo attr : attrs) {
                if (attr instanceof LineNumberTableAttribute) {
                    LineNumberTableAttribute lnt = (LineNumberTableAttribute) attr;
                    for (LineNumberTableAttribute.LineNumberTable table : lnt.getLineNumberTableList()) {
                        lineNumberTable.add(new JLineNumber(table.getStartPc(), table.getLineNumber()));
                    }
                } else if (attr instanceof LocalVariableTable) {

                } else if (attr instanceof StackMapTable) {//栈图

                }
            }
        }
    }

    /**
     * 解析异常表
     *
     * @param code
     * @param constantPool
     */
    private void resolveExceptionTable(Code code, List<ConstantPool> constantPool) {
        List<Code.ExceptionTable> exceptionTable = code.getExceptionTables();
        if (CollectionUtils.isNotEmpty(exceptionTable)) {
            for (Code.ExceptionTable table : exceptionTable) {
                JMethodExceptionTable et = new JMethodExceptionTable();
                et.setStartPC(table.getStartPc());
                et.setEndPC(table.getEndPc());
                et.setHandlerPC(table.getHandlerPc());
                JClassRef ref = (JClassRef) constantPool.get(table.getCatchTypeIndex()).getVal();
                /**
                 * catchTypeIndex为0表示处理所有异常
                 */
                et.setExceptionClassRef(ref);
                this.catchExceptionTable.add(et);
            }
        }
    }

    private void resolveArgSlot() {
        String methodDescriptor = getDescriptor();
        methodDescriptor = methodDescriptor.substring(methodDescriptor.indexOf("(") + 1, methodDescriptor.indexOf(")"));
        for (int i = 0; i < methodDescriptor.length(); i++) {
            this.argSlotCount++;
            String type = "";
            if (methodDescriptor.charAt(i) == 'L') {
                int index = methodDescriptor.indexOf(";", i);
                type = methodDescriptor.substring(i + 1, index);
                i = index;
            } else if (methodDescriptor.charAt(i) == '[') {
                i++;
                if (methodDescriptor.charAt(i) == 'L') {
                    int index = methodDescriptor.indexOf(";", i);
                    type = methodDescriptor.substring(i + 1, index);
                    i = index;
                } else {
                    type = String.valueOf(methodDescriptor.charAt(i));
                    if (type.equals("J") || type.equals("D")) {
                        this.argSlotCount++;
                    }
                }

            } else {
                type = String.valueOf(methodDescriptor.charAt(i));
                if (type.equals("J") || type.equals("D")) {
                    this.argSlotCount++;
                }
            }
        }
        if (!isStatic()) {
            this.argSlotCount++;
        }
    }


    public boolean isNative() {
        return (getAccessFlags() & MethodAccessFlag.ACC_NATIVE.getHex()) == 0 ? false : true;
    }

    public boolean isAbstract() {
        return (getAccessFlags() & MethodAccessFlag.ACC_ABSTRACT.getHex()) == 0 ? false : true;
    }

    /**
     * 获取参数class对象
     *
     * @return
     */
    public JObject[] getParamType() {
        JObject[] js = new JObject[paramType.size()];
        try {
            for (int i = 0; i < paramType.size(); i++) {
                js[i] = getJClass().getLoader().loadJClass(paramType.get(i)).getJObject();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return js;
    }

    public JObject[] getExceptionJClassObj() {
        JObject[] js = new JObject[throwExceptionTable.size()];
        try {
            for (int i = 0; i < throwExceptionTable.size(); i++) {
                JClassRef classRef = new JClassRef(throwExceptionTable.get(i));
                js[i] = classRef.getJClass().getJObject();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return js;
    }

    /**
     * 判断是不是类初始化方法
     *
     * @return
     */
    public boolean isClinit() {
        if ("<clinit>".equals(this.getName()) && "()V".equals(this.getDescriptor())) {
            return true;
        }
        return false;
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    class JLineNumber {
        private Integer startPC;
        private Integer lineNumber;
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    class JMethodExceptionTable {
        private Integer startPC;
        private Integer endPC;
        private Integer handlerPC;
        private JClassRef exceptionClassRef;
    }
}
